use std::{
    cmp::Ordering,
    collections::{HashMap, HashSet},
    sync::Arc,
};

use crate::cache::{model::EntryDetail, ver::Version};
use anyhow::Result;

use uuid::Uuid;
use varisat::{ExtendFormula, Lit};

use super::{
    EntryIndex, PkgIndex, PkgIndexInfo, RepoType, SolveContext, SolveItem, TransationState,
};

fn get_pkg_index(
    satisfied_pkg: &mut HashMap<(String, Option<Version>), PkgIndexInfo>,
) -> Vec<PkgIndexInfo> {
    // for ele in satisfied_pkg {

    // }
    let mut pkgs = satisfied_pkg
        .iter()
        .map(|x| *x.1)
        .collect::<HashSet<PkgIndexInfo>>()
        .iter()
        .map(|x| *x)
        .collect::<Vec<PkgIndexInfo>>();
    pkgs.sort();
    pkgs
    // let mut pkgs = satisfied_pkg
    //     .iter()
    //     .map(|(_, (pkg_index, priority))| (*pkg_index, *priority))
    //     .collect::<Vec<_>>();
    // pkgs.sort_by(|a, b| b.1.cmp(&a.1));
    // pkgs.iter()
    //     .map(|(pkg_index, priority)| *pkg_index)
    //     .collect::<Vec<_>>()
}

impl SolveContext {
    async fn get_pkg_requires_by_uuid(
        &self,
        repo: RepoType,
        pkg_uuid: Uuid,
    ) -> Option<Vec<EntryDetail>> {
        match repo {
            RepoType::Repo(name) => {
                self.repos
                    .iter()
                    .find(|r| r.config.name == name)
                    .unwrap()
                    .get_pkg_requires_by_uuid(pkg_uuid)
                    .await
            }
            RepoType::Cmd => self.cmd_repo.get_pkg_requires_by_uuid(pkg_uuid).await,
            RepoType::Installed => self.installed_repo.get_pkg_requires_by_uuid(pkg_uuid).await,
        }
    }
    async fn get_pkg_version_by_uuid(&self, repo: &RepoType, pkg_uuid: Uuid) -> Option<Version> {
        match repo {
            RepoType::Repo(name) => {
                self.repos
                    .iter()
                    .find(|r| r.config.name.as_str() == name.as_str())
                    .unwrap()
                    .data
                    .clone()
                    .get_pkg_version_by_uuid(pkg_uuid)
                    .await
            }
            RepoType::Cmd => {
                self.cmd_repo
                    .data
                    .clone()
                    .get_pkg_version_by_uuid(pkg_uuid)
                    .await
            }
            RepoType::Installed => {
                self.installed_repo
                    .data
                    .clone()
                    .get_pkg_version_by_uuid(pkg_uuid)
                    .await
            }
        }
    }
    pub(super) async fn solve_require(
        &mut self,
        (pkg_index, pkg_uuid, repo): (PkgIndex, Uuid, RepoType),
    ) -> Result<()> {
        let mut handle_items = Vec::new();
        let mut handle_terms = Vec::new();
        if let Some(reqs) = self.get_pkg_requires_by_uuid(repo, pkg_uuid).await {
            for require in reqs {
                if !require.entry_name.starts_with("(") {
                    handle_items.push(tokio::spawn(
                        self.clone().solve_require_item(SolveItem::new(require)),
                    ));
                } else {
                    handle_terms.push(tokio::spawn(
                        self.clone().solve_require_term(require.entry_name),
                    ));
                }
            }
        }
        let mut entries = Vec::new();
        for handle in handle_items {
            entries.push(handle.await??.0);
        }
        for handle in handle_terms {
            entries.push(handle.await??.0);
        }
        let mut formula_lock = self.formula.lock().await;
        for entry_index in entries.iter() {
            formula_lock
                .add_clause(&[Lit::from_dimacs(*entry_index), !Lit::from_dimacs(pkg_index)]);
        }
        drop(formula_lock);
        Ok(())
    }

    pub(super) async fn solve_require_item(
        mut self,
        mut require_item: SolveItem,
    ) -> Result<(EntryIndex, Vec<PkgIndexInfo>)> {
        let entry_require_lock = self.entry_require.lock().await;
        let r = match entry_require_lock.get(&require_item) {
            Some((entry_index, v)) => (*entry_index, v.to_owned()),
            None => {
                drop(entry_require_lock);
                let mut satisfied_pkg: HashMap<(String, Option<Version>), PkgIndexInfo> =
                    HashMap::new();
                self.cmd_satisfy_requie(&mut require_item, &mut satisfied_pkg)
                    .await;
                self.installed_satisfy_require(&mut require_item, &mut satisfied_pkg)
                    .await?;
                // if satisfied_pkg.is_empty() {
                self.repo_satisfy_require(&mut require_item, &mut satisfied_pkg)
                    .await?;
                // }

                let mut pkg_infos = get_pkg_index(&mut satisfied_pkg);
                pkg_infos.sort();

                let mut same_pkg_name_lock = self.same_pkg_name.lock().await;
                for ((pkg_name, _ver), pkg_info) in satisfied_pkg {
                    // if pkg_name == "perl-libs" {
                    //     dbg!(&require_item);
                    //     dbg!(pkg_info);
                    // }
                    match same_pkg_name_lock.get_mut(&pkg_name) {
                        Some(set) => {
                            set.insert(pkg_info.pkg_index);
                        }
                        None => {
                            let mut set = HashSet::new();
                            set.insert(pkg_info.pkg_index);
                            same_pkg_name_lock.insert(pkg_name, set);
                        }
                    }
                }

                drop(same_pkg_name_lock);

                let entry_index = self.get_latest_index();
                self.update_appeared(&pkg_infos.iter().map(|x| x.pkg_index).collect::<Vec<_>>())
                    .await;
                let mut entry_require_lock_again = self.entry_require.lock().await;
                let mut index_entry_lock = self.index_map_entry.lock().await;
                index_entry_lock.insert(entry_index, require_item.clone());
                entry_require_lock_again.insert(require_item, (entry_index, pkg_infos.clone()));
                drop(entry_require_lock_again);
                drop(index_entry_lock);
                (entry_index, pkg_infos)
            }
        };
        Ok(r)
    }

    async fn cmd_satisfy_requie(
        &mut self,
        item: &mut SolveItem,
        satisfied_pkg: &mut HashMap<(String, Option<Version>), PkgIndexInfo>,
    ) {
        if let Some(cmd_install) = self
            .cmd_repo
            .data
            .clone()
            .get_what_pkg_provide_entry_by_name(item.entry_name.clone())
            .await
        {
            for (pkg_uuid, entry) in cmd_install {
                let cmd_install_ver = Version::from_entry_detail(&entry);
                let cmd_install_flag = entry.flags;
                if item.check_version(&cmd_install_ver, &cmd_install_flag) {
                    self.handle_satisfy(
                        &entry.arch,
                        entry.pkg_name.clone(),
                        pkg_uuid,
                        Some(cmd_install_ver.clone()),
                        None,
                        RepoType::Cmd,
                        satisfied_pkg,
                    )
                    .await;
                }
            }
        } else if item.entry_name.starts_with("/") {
            if let Some(cmd_install) = self
                .cmd_repo
                .data
                .clone()
                .get_what_pkg_provide_file_by_path(item.entry_name.clone())
                .await
            {
                for (pkg_uuid, file_detail) in cmd_install {
                    self.handle_satisfy(
                        &file_detail.arch,
                        file_detail.pkg_name.clone(),
                        pkg_uuid,
                        None,
                        Some(TransationState::ToInstall),
                        RepoType::Cmd,
                        satisfied_pkg, // satiesfied,
                    )
                    .await;
                }
            };
        }
    }
    async fn handle_satisfy(
        &mut self,
        arch: &str,
        pkg_name: String,
        pkg_uuid: Uuid,
        ver: Option<Version>,
        state: Option<TransationState>,
        repo: RepoType,
        satisfied_pkg: &mut HashMap<(String, Option<Version>), PkgIndexInfo>,
    ) {
        let ver = match ver {
            Some(v) => match &v.version {
                Some(_) => Some(v),
                None => self.get_pkg_version_by_uuid(&repo, pkg_uuid).await,
            },
            None => self.get_pkg_version_by_uuid(&repo, pkg_uuid).await,
        };
        let repo_priority = self.get_repo_priority(&repo);
        let state = match state {
            Some(s) => s,
            None => match &repo {
                RepoType::Installed => TransationState::Installed,
                RepoType::Repo(_) => TransationState::ToInstall,
                RepoType::Cmd => TransationState::ToInstall,
            },
        };

        let arch = self.get_arch_priority(arch);
        if arch != 0 {
            match satisfied_pkg.get_mut(&(pkg_name.clone(), ver.clone())) {
                Some(pkg_info) => {
                    if arch > pkg_info.arch_priority && repo_priority >= pkg_info.repo_priority {
                        pkg_info.pkg_index = self
                            .add_pkg_info(Arc::new(pkg_name.to_owned()), pkg_uuid, repo, state)
                            .await;
                        match state {
                            TransationState::Installed => {
                                self.update_installed(pkg_info.pkg_index).await;
                            }
                            _ => {}
                        }
                        pkg_info.arch_priority = arch;
                        pkg_info.repo_priority = repo_priority;
                    }
                }
                None => {
                    let pkg_index = self
                        .add_pkg_info(Arc::new(pkg_name.to_owned()), pkg_uuid, repo, state)
                        .await;
                    match state {
                        TransationState::Installed => {
                            self.update_installed(pkg_index).await;
                        }
                        _ => {}
                    }
                    satisfied_pkg.insert(
                        (pkg_name, ver),
                        PkgIndexInfo {
                            pkg_index,
                            arch_priority: arch,
                            repo_priority,
                        },
                    );
                }
            }
        }
    }
    async fn installed_satisfy_require(
        &mut self,
        item: &mut SolveItem,
        satisfied_pkg: &mut HashMap<(String, Option<Version>), PkgIndexInfo>,
    ) -> Result<()> {
        if let Some(installeds) = self
            .installed_repo
            .data
            .clone()
            .get_what_pkg_provide_entry_by_name(item.entry_name.clone())
            .await
        {
            for (pkg_uuid, entry) in installeds {
                let installed_ver = Version::from_entry_detail(&entry);
                let installed_flag = entry.flags;
                if item.check_version(&installed_ver, &installed_flag) {
                    self.handle_satisfy(
                        &entry.arch,
                        entry.pkg_name.clone(),
                        pkg_uuid,
                        Some(installed_ver.clone()),
                        None,
                        RepoType::Installed,
                        satisfied_pkg,
                    )
                    .await;
                } else {
                    self.installed_update_or_downgrade(
                        Arc::new(entry.pkg_name),
                        item,
                        Some(&installed_ver),
                        satisfied_pkg,
                    )
                    .await?;
                }
            }
        } else if item.entry_name.starts_with("/") {
            if let Some(installed) = self
                .installed_repo
                .data
                .clone()
                .get_what_pkg_provide_file_by_path(item.entry_name.clone())
                .await
            {
                for (pkg_uuid, file_detail) in installed {
                    self.handle_satisfy(
                        &file_detail.arch,
                        file_detail.pkg_name.clone(),
                        pkg_uuid,
                        None,
                        None,
                        RepoType::Installed,
                        satisfied_pkg, // satiesfied,
                    )
                    .await;
                    // self.installed_update_or_downgrade(
                    //     Arc::new(file_detail.pkg_name),
                    //     item,
                    //     None,
                    //     satisfied_pkg,
                    // )
                    // .await?;
                }
            };
        };
        Ok(())
    }
    async fn installed_update_or_downgrade(
        &mut self,
        pkg_name: Arc<String>,
        require_item: &mut SolveItem,
        installed_ver: Option<&Version>,
        satisfied_pkg: &mut HashMap<(String, Option<Version>), PkgIndexInfo>,
    ) -> Result<()> {
        if let Ok(s) = self.which_repo_provide_pkg(pkg_name.clone()).await {
            let mut handles = Vec::new();
            for (repo, pkg_uuid) in s {
                handles.push((
                    RepoType::Repo(repo.config.name.clone()),
                    pkg_uuid,
                    tokio::spawn(repo.data.get_pkg_provides_by_uuid(pkg_uuid)),
                ));
            }
            for (repo, pkg_uuid, handle) in handles {
                if let Some(provides) = handle.await? {
                    if let Some(entry) = provides
                        .iter()
                        .find(|e| e.entry_name == require_item.entry_name.as_str())
                    {
                        let entry_ver = Version::from_entry_detail(entry);
                        if require_item.check_version(&entry_ver, &entry.flags) {
                            let state = match installed_ver {
                                Some(installed) => match entry_ver.partial_cmp(installed) {
                                    Some(Ordering::Greater) => Some(TransationState::Upgrade),
                                    Some(Ordering::Less) => Some(TransationState::Downgrade),
                                    _ => {
                                        continue;
                                    }
                                },
                                None => None,
                            };
                            self.handle_satisfy(
                                &entry.arch,
                                entry.pkg_name.clone(),
                                pkg_uuid,
                                Some(entry_ver),
                                state,
                                repo,
                                satisfied_pkg,
                            )
                            .await;
                        };
                    }
                }
            }
            // for (repo, pkg_uuid, handle) in handles {
            //     if let Some(pkg_detail) = handle.await? {
            //         if require_item.check_version(&pkg_detail.version, &Some("EQ".to_string())) {
            //             let state = match installed_ver {
            //                 Some(installed) => match pkg_detail.version.partial_cmp(installed) {
            //                     Some(Ordering::Greater) => Some(TransationState::Upgrade),
            //                     Some(Ordering::Less) => Some(TransationState::Downgrade),
            //                     _ => None,
            //                 },
            //                 None => None,
            //             };
            //             self.handle_satisfy(
            //                 &pkg_detail.arch,
            //                 pkg_detail.name,
            //                 pkg_uuid,
            //                 Some(pkg_detail.version),
            //                 state,
            //                 repo,
            //                 satisfied_pkg,
            //             )
            //             .await;
            //         };
            //     }
            // }
        }
        Ok(())
    }
    // async fn installed_but_ver_not_satisfied(
    //     &mut self,
    //     pkg_name: Arc<String>,
    //     item: &mut RequireItem,
    //     installed_ver: &Version,
    //     satiesfied: &mut HashMap<String, u8>,
    // ) -> Result<()> {
    //     if let Ok(mut s) = self.which_repo_provide_pkg(pkg_name.clone()).await {
    //         let mut handles = Vec::new();
    //         //which_repo_provide_pkg sort_by repo's priority
    //         //if multiple software packages with the same name and arch meet the requirements,
    //         //select the package with the highest repo's highest priority.
    //         while let Some((repo, pkg_uuid)) = s.pop() {
    //             handles.push((
    //                 RepoType::Repo(repo.config.name.clone()),
    //                 pkg_uuid,
    //                 tokio::spawn(repo.data.get_pkg_detail_by_uuid(pkg_uuid)),
    //             ));
    //         }
    //         for (repo_name, pkg_uuid, handle) in handles {
    //             if let Some(pkg_detail) = handle.await? {
    //                 if item.check_version(&pkg_detail.version, &Some("EQ".to_string())) {
    //                     let name_arch = (pkg_name.to_string(), pkg_detail.arch.clone());
    //                     if !satiesfied.contains(&name_arch) {
    //                         let state = match pkg_detail.version.partial_cmp(installed_ver) {
    //                             Some(Ordering::Greater) => TransationState::Upgrade,
    //                             Some(Ordering::Less) => TransationState::Downgrade,
    //                             _ => {
    //                                 continue;
    //                             }
    //                         };
    //                         let index = self
    //                             .add_pkg_info(pkg_name.clone(), pkg_uuid, repo_name, state)
    //                             .await;
    //                         satiesfied.insert(name_arch);
    //                         item.add_index_with_arch(index, pkg_detail.arch);
    //                     };
    //                 };
    //             };
    //         }
    //     }
    //     Ok(())
    // }
    async fn repo_satisfy_require(
        &mut self,
        item: &mut SolveItem,
        satisfied_pkg: &mut HashMap<(String, Option<Version>), PkgIndexInfo>,
    ) -> Result<()> {
        if let Some(v) = self.which_repo_pkg_provide_entry(&item.entry_name).await? {
            for (repo, provides) in v {
                for (pkg_uuid, entry) in provides {
                    let provide_ver = Version::from_entry_detail(&entry);
                    let provide_flag = entry.flags;
                    if item.check_version(&provide_ver, &provide_flag) {
                        self.handle_satisfy(
                            &entry.arch,
                            entry.pkg_name,
                            pkg_uuid,
                            Some(provide_ver),
                            None,
                            repo.clone(),
                            satisfied_pkg,
                        )
                        .await
                    };
                }
            }
        } else if item.entry_name.starts_with("/") {
            if let Some(v) = self.which_repo_pkg_provide_file(&item.entry_name).await? {
                for (repo, provides) in v {
                    for (pkg_uuid, entry) in provides {
                        self.handle_satisfy(
                            &entry.arch,
                            entry.pkg_name,
                            pkg_uuid,
                            None,
                            None,
                            repo.clone(),
                            satisfied_pkg,
                        )
                        .await;
                    }
                }
            }
        }

        Ok(())
    }
}
